package AddonSubdirectory;

use strict;
use warnings;

use File::Spec;
use File::Spec::Win32;

BEGIN {
    eval { require XML::Parser::Expat; } and eval { import XML::Parser::Expat; };
}
my $has_xml_expat = ( defined &XML::Parser::Expat::new );

BEGIN {
    use Exporter ();
    our ( $VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS );

    $VERSION     = sprintf( "%d", q$Revision$ =~ m{\d+} );
    @ISA         = qw(Exporter);
    @EXPORT      = qw();
    %EXPORT_TAGS = ();
    @EXPORT_OK   = qw();
} ## end BEGIN

# Fields
use constant field_addon_name => 0;
use constant field_dir_name   => 1;
use constant field_node_path  => 2;
use constant field_dirs       => 3;
use constant field_files      => 4;
use constant field_toc        => 5;
use constant field_parent     => 6;
use constant field_children   => 7;
use constant field_up         => 8;
use constant field_info       => 9;
use constant field_valid      => 10;

sub get_addon_name($ ) { $_[ 0 ]->[ &field_addon_name ]; }
sub get_dir_name($ )   { $_[ 0 ]->[ &field_dir_name ]; }
sub get_node_path($ )  { $_[ 0 ]->[ &field_node_path ]; }
sub get_dirs($ )       { $_[ 0 ]->[ &field_dirs ]; }
sub get_files($ )      { $_[ 0 ]->[ &field_files ]; }
sub get_toc($ )        { $_[ 0 ]->[ &field_toc ]; }
sub set_toc($$ )       { $_[ 0 ]->[ &field_toc ] = $_[ 1 ]; $_[ 0 ]; }
sub get_parent($ )     { $_[ 0 ]->[ &field_parent ]; }
sub get_children($ )   { my $res = $_[ 0 ]->[ &field_children ]; return keys %$res; }
sub get_up_dir($ )     { $_[ 0 ]->[ &field_up ]; }
sub get_root($ );
sub is_root($ ) { !defined $_[ 0 ]->[ &field_up ]; }
sub find_dir($$ );
sub find_path($$ );
sub need_path($$ );
sub get_info($ )   { $_[ 0 ]->[ &field_info ]; }
sub is_valid($ )   { $_[ 0 ]->[ &field_valid ]; }
sub make_valid($ ) { $_[ 0 ]->[ &field_valid ] = 1; $_[ 0 ]; }

# AddonSubdirectory->new_root( $node_path,$ref_addon_table[,$addon_name] )
sub new_root ($$$;$ ) {
    my $this            = shift;
    my $node_path       = shift;
    my $ref_addon_table = shift;
    my $addon           = shift;
    my $class           = ref( $this ) || $this;
    my $node            = $node_path->get_name;
    $addon = $node unless defined $addon;
    my $self = [];
    $self->[ &field_addon_name ] = $addon;
    $self->[ &field_dir_name ]   = $node;
    $self->[ &field_node_path ]  = $node_path;
    $self->[ &field_dirs ]       = {};
    $self->[ &field_files ]      = {};
    $self->[ &field_children ]   = {};
    $self->[ &field_info ]       = AddonInfo->lookup( $addon );
    bless $self, $class;
    $ref_addon_table->{ $addon } = $self;
    return $self;
} ## end sub new_root ($$$;$ )

# $node->new_subroot( $node_path,$ref_addon_table[,$addon_name] )
sub new_subroot ($$;$ ) {
    my $this            = shift;
    my $node_path       = shift;
    my $ref_addon_table = shift;
    my $addon           = shift;
    my $class           = ref( $this );
    return undef unless defined $class;
    my $node = $node_path->get_name;
    $addon = $node unless defined $addon;
    my $self = [];
    $self->[ &field_addon_name ] = $addon;
    $self->[ &field_dir_name ]   = $node;
    $self->[ &field_node_path ]  = $node_path;
    $self->[ &field_dirs ]       = {};
    $self->[ &field_files ]      = {};
    $self->[ &field_parent ]     = $this->get_addon_name;
    $self->[ &field_children ]   = {};
    $self->[ &field_info ]       = $this->[ &field_info ];
    bless $self, $class;
    $ref_addon_table->{ $addon } = $self;
    my $root = $ref_addon_table->{ $this->get_addon_name };
    $root->[ &field_children ]->{ $addon } = $self;
    return $self;
} ## end sub new_subroot ($$;$ )

# AddonSubdirectory->new_subroot(
# $node->split_root($ref_addon_table[,$addon_name])
sub split_root ($$;$ ) {
    my $this            = shift;
    my $ref_addon_table = shift;
    my $addon           = shift;
    my $class           = ref( $this );
    return undef unless defined $class;
    my $node_path = $this->[ &field_node_path ];
    my $node      = $node_path->get_name;
    $addon = $node unless defined $addon;

    # Check if split already exists
    my $self = $ref_addon_table->{ $addon };
    if ( !defined $self ) {
        $self                        = [];
        $self->[ &field_addon_name ] = $addon;
        $self->[ &field_dir_name ]   = $node;
        $self->[ &field_node_path ]  = $node_path;
        $self->[ &field_dirs ]       = {};
        $self->[ &field_files ]      = {};
        $self->[ &field_parent ]     = $this->get_addon_name;
        $self->[ &field_children ]   = {};
        $self->[ &field_info ]       = $this->[ &field_info ];
        bless $self, $class;
        $ref_addon_table->{ $addon } = $self;
    } ## end if ( !defined $self )
    my $root = $ref_addon_table->{ $this->get_addon_name };
    $root->[ &field_children ]->{ $addon } = $self;
    return $self;
} ## end sub split_root ($$;$ )

# child: $node->new_subdir($node_path[,$node_name])
sub new_subdir ($$;$ ) {
    my $this      = shift;
    my $node_path = shift;
    my $class     = ref( $this );
    return undef unless defined $class;
    my $node = shift;
    $node = $node_path->get_name unless defined $node;
    my $self = [];
    $self->[ &field_addon_name ] = $this->get_addon_name;
    $self->[ &field_dir_name ]   = $node;
    $self->[ &field_node_path ]  = $node_path;
    $self->[ &field_dirs ]       = {};
    $self->[ &field_files ]      = {};
    $self->[ &field_up ]         = $this;
    $self->[ &field_children ]   = {};
    $self->[ &field_info ]       = $this->get_info;
    bless $self, $class;
    $this->[ &field_dirs ]->{ uc $node } = $self;
    return $self;
} ## end sub new_subdir ($$;$ )

sub get_root($ ) {
    my $self = shift;
    my $root;
    while ( $self = ( $root = $self )->get_up_dir ) {

        # Empty body
    }
    return $root;
} ## end sub get_root($ )

sub find_dir($$ ) {
    my ( $self, $path ) = @_;
    my @path_bits = File::Spec->splitdir( $path );
    return $self unless @path_bits;

    my $cnode = $self;
    while ( $cnode && @path_bits ) {
        my $piece = shift @path_bits;
        next if ( $piece eq "" || $piece eq File::Spec->curdir );
        if ( $piece eq File::Spec->updir ) {
            $cnode = $cnode->[ &field_up ];
        }
        else {
            $cnode = $cnode->get_dirs->{ uc $piece };
        }
    } ## end while ( $cnode && @path_bits)
    return $cnode;
} ## end sub find_dir($$ )

sub find_path($$ ) {
    my ( $self, $path ) = @_;
    my @path_bits = File::Spec::Win32->splitdir( $path );
    return ( 1, $self ) unless @path_bits;
    return ( 1, $self ) if ( $path_bits[ 0 ] eq "" );
    my $fname = pop @path_bits;

    my $cnode = $self;
    while ( $cnode && @path_bits ) {
        my $piece = shift @path_bits;
        next if ( $piece eq "" || $piece eq File::Spec::Win32->curdir );
        if ( $piece eq File::Spec::Win32->updir ) {
            $cnode = $cnode->[ &field_up ];
            return ( 1, $self ) unless $cnode;
        }
        else {
            $cnode = $cnode->get_dirs->{ uc $piece };
        }
    } ## end while ( $cnode && @path_bits)
    return ( $cnode && $cnode->get_files->{ uc $fname }, $cnode );
} ## end sub find_path($$ )

my $read_xml_ui_file_res;
my @read_xml_ui_file_files = ();

sub read_xml_ui_file_start($$@ ) {
    my $parser = shift;
    my $elem   = shift;

    return if ( $elem =~ m{^Ui$} );
    unless ( $elem =~ m{^(?:Script|Include)$}i ) {
        $read_xml_ui_file_res = "Strange key: " . $elem . "\n";
        $parser->finish;
        return;
    }
    return unless @_;
    my $key = shift;
    unless ( $key =~ m{^file$}i ) {
        $read_xml_ui_file_res = "Strange subkey: " . $elem . "/" . $key . "\n";
        $parser->finish;
        return;
    }
    push @read_xml_ui_file_files, shift;
    if ( @_ ) {
        $key                  = shift;
        $read_xml_ui_file_res = "Strange subkey: " . $elem . "/" . $key . "\n";
        $parser->finish;
        return;
    } ## end if ( @_ )
} ## end sub read_xml_ui_file_start($$@ )

sub read_xml_ui_file_char($$ ) {
    my $parser = shift;
    my $text   = shift;

    unless ( $text =~ m{^\s*$} ) {
        $read_xml_ui_file_res = "Strange text: " . $text . "\n";
        $parser->finish;
    }
} ## end sub read_xml_ui_file_char($$ )

sub read_xml_ui_file($ ) {
    my $file = shift;

    my $parser = new XML::Parser::Expat;
    $parser->setHandlers(
        'Start' => \&read_xml_ui_file_start,
        'Char'  => \&read_xml_ui_file_char
    );

    $read_xml_ui_file_res   = undef;
    @read_xml_ui_file_files = ();
    eval { $parser->parsestring( $file->get_contents ); };
    return "Unable to read: " . $file . "\n" if $@;
    $parser->release;
    return $read_xml_ui_file_res if defined $read_xml_ui_file_res;
    return ( undef, @read_xml_ui_file_files );
} ## end sub read_xml_ui_file($ )

sub need_path($$ ) {
    my ( $self,  $path ) = @_;
    my ( $cnode, $root ) = $self->find_path( $path );
    return undef  unless $cnode;
    return $cnode unless ref $cnode;
    return $cnode unless ( $has_xml_expat && $path =~ m{\.xml$}i );
    my $fname = $cnode->[ 1 ];

    my ( $res, @nested ) = read_xml_ui_file( $fname );
    return $cnode if $res;
    foreach my $data ( @nested ) {
        if ( $root->need_path( $data ) ) {
            return $cnode;
        }
    }

    return undef;
} ## end sub need_path($$ )

1;
